﻿Imports Microsoft.LightSwitch.Model
Imports Microsoft.LightSwitch.Presentation
Imports Microsoft.LightSwitch.Presentation.Extensions
Imports Microsoft.LightSwitch.Client
Imports Microsoft.LightSwitch
Imports Microsoft.LightSwitch.Details
Imports System.Reflection
Imports System.Diagnostics
Imports System.Linq.Expressions
Imports Microsoft.LightSwitch.Sdk.Proxy
Imports System.IO
Imports System.Windows.Media.Imaging

Public Module LightSwitchHelper

    Public Function GetValue(Entity As IEntityObject, FieldName As String) As String
        Dim sValue As String = ""
        Try
            If Not IsNothing(Entity.Details.Properties(FieldName).Value) Then
                If Entity.Details.Properties(FieldName).PropertyType.ToString() = "System.Byte[]" Then
                    sValue = "(Image)"
                ElseIf Entity.Details.Properties(FieldName).PropertyType.ToString().StartsWith("Microsoft.LightSwitch.Framework.EntityCollection") Then
                    sValue = "(Collection)"
                Else
                    sValue = Entity.Details.Properties(FieldName).Value.ToString()
                End If
            End If
        Catch
        End Try
        GetValue = sValue
    End Function

    Public Function GetImage(Entity As IEntityObject, FieldName As String) As Byte()
        Dim bValue As Byte() = Nothing
        Try
            bValue = Entity.Details.Properties(FieldName).Value
        Catch
        End Try
        GetImage = bValue
    End Function

    Public Function GetValue(Item As String) As String
        Dim sValue As String = ""
        Try
            If Not IsNothing(Item) Then
                If Item = "System.Byte[]" Then
                    sValue = "(Image)"
                ElseIf Item.StartsWith("Microsoft.LightSwitch.Framework.EntityCollection") Then
                    sValue = "(Collection)"
                Else
                    sValue = Item.ToString()
                End If
            End If
        Catch
        End Try
        GetValue = sValue
    End Function

    Public Function GetObjectValue(Obj As Object, FieldName As String) As Object
        Dim ObjProperty As System.Reflection.PropertyInfo
        Dim ObjType As Type = Obj.GetType()
        GetObjectValue = Nothing
        ' Attempt value retrieval through reflection when the type is unknown
        Try
            ObjProperty = ObjType.GetProperty(FieldName)

            If ObjProperty Is Nothing Then
                Dim ObjField As System.Reflection.FieldInfo = ObjType.GetField(FieldName)
                GetObjectValue = ObjField.GetValue(Obj)
            Else
                GetObjectValue = ObjProperty.GetValue(Obj, Nothing)
            End If
        Catch ex As Exception
            
        End Try
        ' Handle possible anonymous type where the value can't be extracted through reflection
        If GetObjectValue Is Nothing Then
            Try
                Dim memberExpression As Expression = Expression.Property(Expression.Constant(Obj), FieldName)
                GetObjectValue = Expression.Lambda(memberExpression).Compile().DynamicInvoke()
            Catch ex As Exception
                GetObjectValue = ObjType.ToString()
            End Try
        End If

    End Function

    Public Function ValidData(value As String, row As Int32, mapping As ColumnMapping, errorList As List(Of String)) As Boolean
        Dim convertedValue As Object = Nothing
        Dim isValid As Boolean = TryConvertValue(mapping.TableField.TypeName, value, convertedValue)
        If isValid = False Then
            errorList.Add("Column:" + mapping.OfficeColumn + " Row:" + row.ToString + " Cannot convert value(" + value + ") to " + mapping.TableField.TypeName + " for '" + mapping.TableField.DisplayName + "'")
        End If
        ValidData = isValid
    End Function

    Public Function ValidData(value As String, row As Int32, mapping As ColumnMapping, collection As IVisualCollection, navProperties As Dictionary(Of String, IEntityObject), errorList As List(Of String)) As Boolean
        Dim bValid As Boolean = False
        Dim targetEntityType As IEntityType = mapping.TableField.EntityType

        'Need to grab the entity set and check number of results we get
        Dim entityContainerDefinition As IEntityContainerDefinition = Nothing
        Dim serviceProxy As IServiceProxy = Microsoft.VisualStudio.ExtensibilityHosting.VsExportProviderService.GetExportedValue(Of IServiceProxy)()
        Dim moduleWithEntitySets As IModuleDefinition =
            (From md As IModuleDefinition In serviceProxy.ModelService.Modules
            Where md.GlobalItems.OfType(Of IEntityContainerDefinition).Any(Function(ecd) ecd.EntitySets.Any(Function(es) es.EntityType Is targetEntityType))).FirstOrDefault()

        If moduleWithEntitySets IsNot Nothing Then
            entityContainerDefinition = (From ecd As IEntityContainerDefinition In moduleWithEntitySets.GlobalItems.OfType(Of IEntityContainerDefinition)()
                                         Where ecd.EntitySets.Any(Function(es) es.EntityType Is targetEntityType)).FirstOrDefault()
        End If

        If entityContainerDefinition Is Nothing Then
            Throw New Exception("Could not find an entity container representing the entity type: " + targetEntityType.Name)
        End If
        Dim entitySetDefinition As IEntitySetDefinition = (From es As IEntitySetDefinition In entityContainerDefinition.EntitySets
                                                           Where es.EntityType Is targetEntityType).First

        Dim entitySet As IEntitySet =
            DirectCast(collection.Screen.Details.DataWorkspace.Details.Properties(entityContainerDefinition.Name).Value, IDataService) _
                .Details.Properties(entitySetDefinition.Name).Value
        Dim dsQuery As IDataServiceQueryable = entitySet.GetQuery()

        'Search for the matching entity for the relationship
        Dim results As IEnumerable(Of IEntityObject) =
            SearchEntityMethodInfo.MakeGenericMethod({dsQuery.ElementType}).Invoke(Nothing, New Object() {dsQuery, value, targetEntityType})

        Dim searchCount As Int32 = results.Count
        If searchCount = 0 Then
            bValid = False
            errorList.Add("Column:" + mapping.OfficeColumn + " Row:" + row.ToString + " Cannot find a matching '" + mapping.TableField.DisplayName + "' for '" + value + "'")
        ElseIf searchCount > 1 Then
            bValid = True
            errorList.Add("Column:" + mapping.OfficeColumn + " Row:" + row.ToString + " Multiple matching '" + mapping.TableField.DisplayName + "' for '" + value + "'.  Will select first match.")
            navProperties(mapping.TableField.Name + "_" + value) = results.FirstOrDefault()
        Else
            bValid = True
            navProperties(mapping.TableField.Name + "_" + value) = results.FirstOrDefault()
        End If
        ValidData = bValid
    End Function

    Public Function GetSummaryProperty(ByVal entityType As IEntityType) As IEntityPropertyDefinition
        'Return the specified property if one is specified
        Dim attribute As ISummaryPropertyAttribute = entityType.Attributes.OfType(Of ISummaryPropertyAttribute)().FirstOrDefault()
        If attribute IsNot Nothing AndAlso attribute.Property IsNot Nothing Then
            Return attribute.Property
        End If


        'If none is specified, try to infer one
        Dim properties As IEnumerable(Of IEntityPropertyDefinition) = entityType.Properties.Where(Function(p) (Not TypeOf p Is INavigationPropertyDefinitionBase) AndAlso (Not p.PropertyType.Name.Contains("Binary")))
        Dim stringProperty As IEntityPropertyDefinition = properties.FirstOrDefault(Function(p) p.PropertyType.Name.Contains("String"))
        If stringProperty Is Nothing Then
            Return properties.FirstOrDefault
        Else
            Return stringProperty
        End If
    End Function

    ' Returns a FieldDefinition object given a collection and a field name. Does not work for collection fields or entity fields
    Public Function GetFieldDefinition(collection As IVisualCollection, FieldName As String) As FieldDefinition
        Dim entityType As IEntityType = collection.Details.GetModel.ElementType
        Dim isNullable As Boolean
        Dim fd As FieldDefinition = Nothing

        For Each p As IEntityPropertyDefinition In entityType.Properties
            'Ignore hidden fields and computed field
            If p.Attributes.Where(Function(a) a.Class.Name = "Computed").FirstOrDefault Is Nothing Then
                If Not TypeOf p.PropertyType Is ISequenceType Then
                    'ignore collections and entities

                    If p.Name = FieldName Then
                        fd = New FieldDefinition()
                        fd.Name = p.Name
                        fd.DisplayName = p.Name
                        fd.TypeName = GetPropertyType(p.PropertyType, isNullable)
                        fd.IsNullable = isNullable
                        If fd.TypeName = "Entity" Then
                            fd.EntityType = DirectCast(p.PropertyType, IEntityType)
                        End If
                        Exit For
                    End If
                End If
            End If
        Next

        Return fd
    End Function


    Public Function GetPropertyType(ByVal p As IDataType, ByRef isNullable As Boolean) As String
        Dim typeName As String = ""
        If TypeOf (p) Is ISemanticType Then
            typeName = DirectCast(p, ISemanticType).UnderlyingType.Name
            isNullable = False
        ElseIf TypeOf (p) Is INullableType AndAlso TypeOf (DirectCast(p, INullableType).UnderlyingType) Is ISemanticType Then
            typeName = DirectCast(DirectCast(p, INullableType).UnderlyingType, ISemanticType).UnderlyingType.Name
            isNullable = True
        ElseIf TypeOf (p) Is INullableType Then
            typeName = DirectCast(p, INullableType).UnderlyingType.Name
            isNullable = True
        ElseIf TypeOf (p) Is ISimpleType Then
            typeName = DirectCast(p, ISimpleType).Name
            isNullable = False
        ElseIf TypeOf (p) Is IEntityType Then
            typeName = "Entity"
            isNullable = True
            'fd.EntityType = DirectCast(p.PropertyType, IEntityType)
        Else
            Throw New NotSupportedException("Could not determine the property type")
        End If
        Return typeName
    End Function

    Public Function IsComputed(ByVal entityProperty As IEntityPropertyDefinition) As Boolean
        Return entityProperty.Attributes.OfType(Of IComputedAttribute).Any()
    End Function

    Public Function TryConvertValue(ByVal propertyType As String, ByVal value As String, ByRef convertedValue As Object) As Boolean
        Dim canConvert As Boolean = False

        Dim convertedTry As Object = Nothing

        Select Case propertyType
            Case "Binary"
                canConvert = False
            Case "Boolean"
                canConvert = Boolean.TryParse(value, convertedTry)
            Case "DateTime"
                canConvert = DateTime.TryParse(value, convertedTry)
            Case "Decimal"
                canConvert = Decimal.TryParse(value, convertedTry)
            Case "Double"
                canConvert = Double.TryParse(value, convertedTry)
            Case "Int32"
                canConvert = Int32.TryParse(value, convertedTry)
            Case "Int16"
                canConvert = Int16.TryParse(value, convertedTry)
            Case "Int64"
                canConvert = Int64.TryParse(value, convertedTry)
            Case "String"
                canConvert = True
                convertedTry = value
            Case Else
                Throw New NotSupportedException()
        End Select

        If canConvert Then
            convertedValue = convertedTry
        End If
        Return canConvert
    End Function



#Region "Generic method to search an entity"
    Private _SearchEntityMethodInfo As MethodInfo

    Public Function SearchEntityMethodInfo() As MethodInfo
        If _SearchEntityMethodInfo Is Nothing Then
            _SearchEntityMethodInfo = GetType(LightSwitchHelper).GetMethod("SearchEntity", BindingFlags.Static Or BindingFlags.NonPublic)
            Debug.Assert(_SearchEntityMethodInfo IsNot Nothing)
        End If
        Return _SearchEntityMethodInfo
    End Function

    Private Function SearchEntity(Of T As IEntityObject)(ByVal query As IDataServiceQueryable(Of T), ByVal value As String, ByVal entityType As IEntityType) As IEnumerable(Of IEntityObject)
        'LOGIC
        'Search PK field (if only one)
        'Search summary field (if not computed)
        'Do a generic search

        If entityType.KeyProperties.Count = 1 Then
            Dim entityKey As IKeyPropertyDefinition = entityType.KeyProperties.First
            Dim propertyType As String = GetPropertyType(entityKey.PropertyType, Nothing)

            Dim convertedValue As Object = Nothing
            If TryConvertValue(propertyType, value, convertedValue) Then
                Dim pe As ParameterExpression = Expression.Parameter(query.ElementType, "entity")
                Dim wherePredicate As Expression = Expression.Equal(Expression.Property(pe, entityKey.Name), Expression.Constant(convertedValue))
                Dim whereExpression As Expression(Of Func(Of T, Boolean)) = Expression.Lambda(wherePredicate, pe)

                Dim keyResults As IEnumerable(Of IEntityObject) = query.Where(whereExpression).Execute.Cast(Of IEntityObject)()
                If keyResults.Count > 0 Then
                    Return keyResults
                End If
            End If
        End If

        Dim summaryProperty As IEntityPropertyDefinition = GetSummaryProperty(entityType)
        If summaryProperty IsNot Nothing AndAlso Not IsComputed(summaryProperty) Then
            Dim propertyType As String = GetPropertyType(summaryProperty.PropertyType, Nothing)

            Dim convertedValue As Object = Nothing
            If TryConvertValue(propertyType, value, convertedValue) Then
                Dim pe As ParameterExpression = Expression.Parameter(GetType(T), "entity")
                Dim wherePredicate As Expression = Expression.Equal(Expression.Property(pe, summaryProperty.Name), Expression.Constant(convertedValue))
                Dim whereExpression As Expression(Of Func(Of T, Boolean)) = Expression.Lambda(wherePredicate, pe)

                Dim summaryResults As IEnumerable(Of IEntityObject) = query.Where(whereExpression).Execute.Cast(Of IEntityObject)()
                If summaryResults.Count > 0 Then
                    Return summaryResults
                End If
            End If
        End If


        Return query.Search({value}).Execute().Cast(Of IEntityObject)()
    End Function
#End Region

End Module